/*
* ============================================================================
*  Name        : sea_parser.cpp
*  Part of     : Finite state machine framework
*  Description : State machine parser implementation
*  Version     : %version: %
*  
*
* ============================================================================
*/
#include "sea_parser.h"

/**
 * @file sea_parser.cpp
 *
 * @brief
 *  State machine source code compiler
 *
 * @details
 *  There are two state machine notations. \b Parser::eBinary presented by the folowing format
 *      @li SZ  - 32 bit Compiled buffer size
 *      @li RSV - 2 x 32 Reserved fields
 *      @li EXM - 32 bit Execution mode type ASYNC, SYNC, ADD
 *      @li NOS - 32 bit Number Of States 
 *      @li ISI - 32 bit Initial State Index. Latter is replaced with the offset after parsing 
 *  Followed by array of states:  <br>
 *      @li HBI - 32 bit HeartBeat Interval is milliseconds 
 *      @li HBN - 32 bit HeartBeats Number 
 *      @li NOR - 32 bit Number Of EAS Records
 *  Followed by array of event records:  
 *      @li EAS - 3 x 32 Event index, Action Index, next State index.
 *                   Latter is replaced with the offset after parsing  <br>
 * Second notation is declarative, resulting in Binary notation after parsing.  <br>
 * Declarative notation can be passed to the parser as a string ( \b Parser::eString )  <br>
 * or as a container file name ( \b Parser::eFile ) <br>
 *
 * @ingroup StateMachineFramework
 */

using namespace sea;

// -------------------------------------------------------------------------------------------------

/**
 * Mother of all state machines. State machine of the state machine compiler
 */
int parser_state_machine_bin[ ] = 
{
    0,                  // Compiled buffer size
    0, 0,               // Reserved

    2,                  // EXM :: RunParser
    14,                 // NOS
    1,                  // ISI
        // 0. Exception
        0, 0, 9,        // HBI HBN NOR
            4, 15, 13,  // EAS
            5, 15, 13,
            6, 15, 13,
            7, 15, 13,
            8, 15, 13,
            9, 15, 13,
           10, 15, 13,
            3, 15, 13,
           12, 15, NEXT_STATE_NOJUMP,
        // 1. SmName
        0, 0, 8,
           -3,  0, NEXT_STATE_NOJUMP,
            0,  2, NEXT_STATE_NOJUMP,
            1, -1, 13,
            2, 15, 13,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,
            8,  4,  2,
            5, -1, 13,
        // 2. ExecMode
        0, 0, 5,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            8,  5,  3,
            9, -1,  4,
        // 3. AtSmEntry
        0, 0, 4,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            9, -1,  4,
        // 4. StateName
        0, 0, 5,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            8,  6,  5,
           10, -1, 12,
        // 5. StateHeartBeatInterval
        0, 0, 6,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            7,  7,  6,
            9,  7, NEXT_STATE_NOJUMP,
            9,  8,  8,
        // 6. StateHeartBeats
        0, 0, 5,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            7,  8,  7,
            9,  8,  8,
        // 7. AtStateEntry
        0, 0, 4,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            9, -1,  8,
        // 8. Event
        0, 0, 6,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            8,  9, NEXT_STATE_NOJUMP,
            8, 10,  9,
           10, -1,  4,
        // 9. Action
        0, 0, 4,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            8, 11, 10,
        // 10. NextState
        0, 0, 4,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

            8, 12, 11,
        // 11. AtStateExit
        0, 0, 6,
           -3,  3, NEXT_STATE_NOJUMP,
            4,  2, NEXT_STATE_NOJUMP,
            6,  3, NEXT_STATE_NOJUMP,

           10, 13,  4,
            8,  9, NEXT_STATE_NOJUMP,
            8, 10,  9,
        // 12. AtSmExit
        0, 0, 2,
           -3,  16, NEXT_STATE_NOJUMP,
           11,  14, 13,
        // 13. "exit"
        0, 0, 1,
           -3, -2, NEXT_STATE_NOJUMP
};

/**
 * Parser's static meta events 
 */
#if defined( STATIC_META_DATA_ENABLED )

EVENT_INT_IMPL( Parser, Ev_Int_InitOk );
EVENT_INT_IMPL( Parser, Ev_Int_InitBinaryOk );
EVENT_INT_IMPL( Parser, Ev_Int_InitFailed );
EVENT_INT_IMPL( Parser, Ev_Int_ParseError );
EVENT_INT_IMPL( Parser, Ev_Int_Eol );
EVENT_INT_IMPL( Parser, Ev_Int_Eof );
EVENT_INT_IMPL( Parser, Ev_Int_NotEmptyString );
EVENT_INT_IMPL( Parser, Ev_Int_GotNumber );
EVENT_INT_IMPL( Parser, Ev_Int_GotName );
EVENT_INT_IMPL( Parser, Ev_Int_GotOpenBracket );
EVENT_INT_IMPL( Parser, Ev_Int_GotCloseBracket );
EVENT_INT_IMPL( Parser, Ev_Int_ResolveOk );
EVENT_INT_IMPL( Parser, Ev_Int_ParseWarning );

#endif

// -------------------------------------------------------------------------------------------------
// eString and eFile parser ctor
// -------------------------------------------------------------------------------------------------
Parser::Parser( ) :
    StateMachine( STATEMACHINE_NAME ),
        m_error_code( eOk ),
        m_type( eString ),
        m_raw_array( NULL ),
        m_literal_buffer( NULL ),
        m_current_ptr( NULL ),
        m_file( NULL ),
        m_state_machine( NULL ),
        m_offset( 0 ),
        m_state_index( 0 ),
        m_event_record_counter( 0 ),
        m_current_state_offset( 0 ),
        m_file_size( 0 )
{
}

// -------------------------------------------------------------------------------------------------
// Reset internal data for another compilation
// -------------------------------------------------------------------------------------------------
void Parser::reset( )
{
    m_error_code = eOk;
    m_raw_array = NULL;
    m_literal_buffer = NULL;
    m_current_ptr = NULL;
    m_file = NULL;
    m_state_machine = NULL;
    m_offset = 0;
    m_state_index = 0;
    m_event_record_counter = 0;
    m_current_state_offset = 0;
    m_file_size = 0;
    m_rt_state_machine.clear( );
    m_nsl2state_name.clear( );
    m_state_name2offset.clear( );
}

// -------------------------------------------------------------------------------------------------
// Parse the source code for the state machine
// -------------------------------------------------------------------------------------------------
ExecCode* Parser::exec( StateMachine* state_machine, const char* str, Parser::Type type )
{
    // The compiler is self aware :), which means it knows what it's going to do
    // and how, so compile its own state machine
    create( 0, parser_state_machine_bin );

    m_type = type;
    m_state_machine = state_machine;

    if ( type == eString )
    {
        // Copy const char* to string
        m_the_string = str;
    }
    else
    if ( type == eFile )
    {
        m_file_name = str;
    }
    else
    {
        // assert
    }

    // Compile state machine source code
    start( RunParser );

    // Make the compiled state machine as sharable object
    ExecCode* result = finalize( );
    
    // Clean internal data
    reset( );

    return result;
}

// -------------------------------------------------------------------------------------------------
// Parse precompiled format for the state machine
// -------------------------------------------------------------------------------------------------
ExecCode* Parser::exec( StateMachine* state_machine, BinCode raw_array )
{
    if ( state_machine != this )
    {
        // Compile state machine
        create( 0, parser_state_machine_bin );

        m_state_machine = state_machine;
        m_type = eBinary;
        m_raw_array = raw_array;

        // Compile == update state machine indecies with the offsets
        // in the binary buffer
        start( RunParser );
    }
    else
    {
        // Compile "Mother" state machine
        m_state_machine = this;
        m_type = eBinary;
        m_raw_array = raw_array;

        m_state_machine->set_exec_mode( RunParser );

        // Compile 
        ASSERT_DEBUG( raw_array == parser_state_machine_bin );
        ParseBinarySm( raw_array );

        // Wipe out the data used for self-compilation
        m_state_machine = NULL;
        m_nsl2state_name.clear( );
        m_state_name2offset.clear( );
        m_state_index = 0;
    }

    // Make the compiled state machine as sharable object
    ExecCode* result = finalize( );
    
    // Clean internal data
    reset( );

    return result;
}

// -------------------------------------------------------------------------------------------------
// Make the compiled state machine as shareable object
// -------------------------------------------------------------------------------------------------
ExecCode* Parser::finalize( )
{
    if ( m_error_code != eOk )
    {
        return NULL;
    }

    size_t size;
    if ( m_type == eString || m_type == eFile )
    {
        if ( m_rt_state_machine.empty( ) )
        {
            return NULL;
        }

        size = m_rt_state_machine.size( );
        m_raw_array = new int[ size ];
        if ( !m_raw_array )
        {
            return NULL;
        }
        
        for( size_t i = 0; i < size; i++ )
        {
            m_raw_array[ i ] = m_rt_state_machine[ i ];
        }
        
        // Set compiled buffer length
        m_raw_array[ eSize ] = size;
    }
    else
    {
        ASSERT_DEBUG( m_raw_array );
        size = m_raw_array[ eSize ];

        ASSERT_DEBUG( size );

        BinCode mem = new int[ size ];
        if ( !mem )
        {
            return NULL;
        }

        memcpy( mem, m_raw_array, size * sizeof( int ) );
        m_raw_array = mem;
    }

    return NEW ExecCode( m_raw_array );
}


// -------------------------------------------------------------------------------------------------
// Init function 
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, init )
{
    SEA_UNUSED_PARAM( ); 
    if ( !m_state_machine )
    {
        push_event( Ev_Int_ParseError( ), eParserNoHostingSm );
        return;
    }

    if ( !m_state_machine->registry( ) )
    {
        push_event( Ev_Int_ParseError( ), eParserWrongMetaData );
        return;
    }

    // Ev_Int_InitFailed
    // Ev_Ini_InitOk
    if ( m_type == eFile )
    {
        if ( fopen_s( &m_file, m_file_name.c_str( ), "rb" ) != 0 )
        {
            push_event( Ev_Int_InitFailed( ), eParserCannotOpenFile );
            return;
        }
        
        // Get file size
        fseek( m_file, 0, SEEK_END );
        m_file_size = ftell( m_file );

        if ( m_file_size == -1 )
        {
            // eParserFileReadError
            push_event( Ev_Int_InitFailed( ), "Cannot get file size" );
            return;
        }
        fseek( m_file, 0, SEEK_SET );
        
        if ( ( m_literal_buffer = new char[ m_file_size ] ) != NULL )
        {
            push_event( Ev_Int_InitOk( ) );
        }
        else
        {
            // eOutOfMemory
            push_event( Ev_Int_InitFailed( ), "Out of Memory" );
        }
        return;
    }

    if( m_type == eString )
    {
        if ( m_the_string.empty( ) )
        {
            // eParserEmtpySourceCode
            push_event( Ev_Int_InitFailed( ),"Empty state machine body" );
        }
        else
        {
            m_current_ptr = const_cast< char* >( m_the_string.c_str( ) );
            push_event( Ev_Int_InitOk( ) );
        }
        return;
    }

    if( m_type == eBinary )
    {
        ParseBinarySm( m_raw_array );
    }
    else
    {
        // eParserUnknownSourceType
        push_event( Ev_Int_InitFailed( ), "Unknown state machine body type" );
    }
}

// -------------------------------------------------------------------------------------------------
// Validate and resolve next state offset in raw state machine
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, ParseBinarySm )
{
    BinCode body = reinterpret_cast< int* >( ACT_EVENT_PARAM( ) );
    BinCode init = body;

    if ( body[ eSize ] != 0 )
    {
        // push_event( Ev_Int_ParseWarning( ), "Binary code already compiled" );
        push_event( Ev_Int_InitBinaryOk( ) );
        return;
    }

    // Skip reserved fileds
    body += Parser::eNumberOfStates;

    // Number of states
    int nos = *body++;

    // Initial State Index
    int* isi = body++;

    if ( *isi == 0 || *isi >= nos )
    {
        push_event( Ev_Int_ParseError( ), eParserInvalidInitialStateIndex );
        return;
    }

    // Map of state index to state offset in the binary
    // buffer
    map< int, i::uint32 > index2offset;

    MetaSystemRegistryEntry* registry = m_state_machine->registry( );
    MetaSystemRegistryEntry* sysRegistry = StateMachine::system( )->registry( );

    int actions_size = registry->m_actions.size( );
    int events_size = registry->m_events.size( );

    // Go through all states
    for( int i = 0; i < nos; ++i )
    {
        // Map state index with the offset in the binary buffer
        index2offset[ i ] = body - init;

#if defined( SEA_LOG )
        registry->m_offset2index[ body - init ] = i;
#endif

        // Skip HBI and HBN - cannot validate them
        body += 2;

        // Get number of the state records
        int nor = *body++;

        // Go through all event records for the state
        for( int j = 0; j < nor; ++j )
        {
            MetaEvent the_event;
            if ( *body < 0 )
            {
                int index = ( -1 * ( *body ) ) - 1;
                if ( index < events_size )
                {
                    the_event = sysRegistry->m_events[ index ];
                }
                else
                {
                    push_event( Ev_Int_ParseError( ), eParserUnknownSystemEvent );
                    return;
                }
            }
            else
            {
                if ( *body < events_size )
                {
                    the_event = registry->m_events[ *body ];
                }
                else
                {
                    push_event( Ev_Int_ParseError( ), eParserUnknownEvent );
                    return;
                }
            }
            
            // Resolve system and internal events
            *body = the_event.index( );
            body++;

            if ( *body != NULL_ACTION )
            {
                MetaAction the_action;
                if ( *body >= 0  )
                {
                    if ( *body < actions_size )
                    {
                        the_action = registry->m_actions[ *body ];
                    }
                    else
                    {
                        push_event( Ev_Int_ParseError( ), eParserUnknownAction );
                        return;
                    }
                }
                else
                {
                    int index = ( -1 * ( *body ) ) - 2;
                    if ( index < actions_size )
                    {
                        the_action = sysRegistry->m_actions[ index ];
                    }
                    else
                    {
                        push_event( Ev_Int_ParseError( ), eParserUnknownSystemAction );
                        return;
                    }

                    // Resolve system actions
                    *body = the_action.index( );
                }
            }

            body++;

            if ( *body != NEXT_STATE_NOJUMP && *body != NEXT_STATE_COUNT )
            {
                if ( *body <= 0 || *body >= nos )
                {
                    push_event( Ev_Int_ParseError( ), eParserUnknownState );
                    return;
                }
            }
            body++;
        }
    }

    // Second pass to resolve state indecies
    body = init;
    body += Parser::eNumberOfStates;
    nos = *body++;
    body++;

    for( int i = 0; i < nos; ++i )
    {
        body += 2;
        int nor = *body++;

        for( int j = 0; j < nor; ++j )
        {
            body += 2;
            if ( *body != NEXT_STATE_NOJUMP && *body != NEXT_STATE_COUNT )
            {
                *body = index2offset[ *body ];
            }
            body++;
        }
    }

    // And finally resolve initial state index
    *isi = index2offset[ *isi ];

    // Fill 1st word with the compiled buffer length
    init[ eSize ] = body - init;

    // We are good
    push_event( Ev_Int_InitBinaryOk( ) );
}

// -------------------------------------------------------------------------------------------------
// Read string from a file
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, readString )
{
    SEA_UNUSED_PARAM( );
    // Read string from file
    //  Ev_Int_Eof
    //  Ev_Int_NotEmptyString
    //  Ev_Int_Eol
    if ( m_type == eFile )
    {
        if ( fgets( m_literal_buffer, m_file_size, m_file ) == NULL )
        {
            push_event( Ev_Int_Eof( ) );
            return;
        }

        m_current_ptr = m_literal_buffer;
    }

    if ( strlen( m_current_ptr ) > 0 )
    {
        push_event( Ev_Int_NotEmptyString( ) );
        return;
    }

    if ( m_type == eFile )
    {
        push_event( Ev_Int_Eol( ) );
    }
    else
    {
        push_event( Ev_Int_Eof( ) );
    }
}

// -------------------------------------------------------------------------------------------------
// Get literal from a string
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, getLiteral )
{
    SEA_UNUSED_PARAM( ); 

    // Parse the string to find a literal string
    //  Ev_Int_Eol
    //  Ev_Int_GotName
    //  Ev_Int_GotNumber
    //  Ev_Int_GetOpenBracket
    //  Ev_Int_GotCloseBracket
    char* token = strtok_s( NULL, " ,\n\t\r", &m_current_ptr );
    if ( token == NULL )
    {
        push_event( Ev_Int_Eol( ) );
    }
    else        
    if ( *token == '{' )
    {
        push_event( Ev_Int_GotOpenBracket( ) );
    }
    else
    if ( *token == '}' )
    {
        push_event( Ev_Int_GotCloseBracket( ) );
    }
    else
    if ( isdigit( *token ) )
    {
        int number = atoi( token );
        push_event( Ev_Int_GotNumber( ), ( void* )number );
    }
    else
    {
        push_event( Ev_Int_GotName( ), ( void* )token );
    }
}

// -------------------------------------------------------------------------------------------------
// Create runtime state machine
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, createStateMachine )
{
    char* name = reinterpret_cast< char* >( ACT_EVENT_PARAM( ) );
    sea::ref< MetaSystemRegistryEntry > registry_entry = GlobalMetaSystemRegistry::get( )[ name ];

    // Check the state machine name is registered with GlobalMetaSystemRegistry
    if ( !registry_entry )
    {
        push_event( Ev_Int_ParseError( ), eParserUnknownStateMachine );
        return;
    }
    
    // Init reserved fields eSize, eReserved01, eReserved02
    m_rt_state_machine.insert( m_rt_state_machine.end( ), 0 ); 
    m_rt_state_machine.insert( m_rt_state_machine.end( ), -1 );
    m_rt_state_machine.insert( m_rt_state_machine.end( ), -1 );
    m_offset += 3;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Store execution mode
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, storeExecMode )
{
    String exec_mode = reinterpret_cast< char* >( ACT_EVENT_PARAM( ) );

    if ( exec_mode == "async" )
    {
        // Parent launches a thread for each state machine instance
        m_rt_state_machine.insert( m_rt_state_machine.end( ), StateMachine::RunAsync );
    }
    else
    if ( exec_mode == "async_batch" )
    {
        // Parent launches only one thread for all state machine instances
        m_rt_state_machine.insert( m_rt_state_machine.end( ), StateMachine::RunAsyncBatch );
    }
    else
    if ( exec_mode == "sync" )
    {
        // Parent launches one state machine and waits for completion
        m_rt_state_machine.insert( m_rt_state_machine.end( ), StateMachine::RunSync );
    }
    else
    if ( exec_mode == "parser" )
    {
        // Parent launces state machine in the same thread it runs himself
        m_rt_state_machine.insert( m_rt_state_machine.end( ), StateMachine::RunParser );
    }
    else
    if ( exec_mode == "user_dispatched" )
    {
        // Parent launces state machine in the same thread it runs himself
        m_rt_state_machine.insert( m_rt_state_machine.end( ), StateMachine::RunDispatched );
    }
    else
    if ( exec_mode == "" )
    {
        m_rt_state_machine.insert( m_rt_state_machine.end( ), StateMachine::RunDefault );
    }
    else
    {
        push_event( Ev_Int_ParseError( ), eParserUnknownExecMode );
        return;
    }
    m_offset++;

    // Reserve location for Number of States
    m_rt_state_machine.insert( m_rt_state_machine.end( ), 0 ); 
    m_offset++;

    // Reserve location for Initital State Index
    m_rt_state_machine.insert( m_rt_state_machine.end( ), 1 );
    m_offset++;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Create a runtime state
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, createState )
{
    char* stateName = reinterpret_cast< char* >( ACT_EVENT_PARAM( ) );

#if defined( SEA_LOG ) || defined( STORE_BIN_CODE )
    MetaSystemRegistryEntry* registry = m_state_machine->registry( );
    registry->m_offset2index[ m_offset ] = m_state_index;
    registry->m_offset2name[ m_offset ] = string( stateName );
#endif

    // For string/file source code compilation - 
    // state #1 is always the initial one.
    // However, if the state machine has only EXCEPTION state
    // we have to set it as initial state,
    // so put buffer offset for states with index 0 and 1
    // If more then 1 state found - it will be overwritten
    // by state offset #1
    if ( m_state_index <= 1 )
    {
        m_rt_state_machine[ eInitialStateIndex ] = m_offset;
    }

    // Count the states to fill out NOS at the end of compilcation
    m_state_index++;

    // Keep track of all states and corresponding indecies
    m_state_name2offset[ stateName ] = m_offset;

    // Current state offset
    // Save location to store # of event records
    // that is current plus HBI and HBN
    // Alos check if counting state, if any, is valid
    m_current_state_offset = m_offset;

    // Reset event recods/EAS counter for the state
    m_event_record_counter = 0;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Store heart beat interval for a currently parsed state 
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, storeHeartBeatInterval )
{
    m_rt_state_machine.insert( m_rt_state_machine.end( ), reinterpret_cast< int >( ACT_EVENT_PARAM( ) ) );
    m_offset++;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Store heart beat count for a currently parsed state 
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, storeHeartBeats )
{
    m_rt_state_machine.insert( m_rt_state_machine.end( ), reinterpret_cast< int >( ACT_EVENT_PARAM( ) ) );

    // Reset NOR to 0
    // To be filled when state parsing is finished
    m_rt_state_machine.insert( m_rt_state_machine.end( ), 0 );
    m_offset += 2;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Create event record (search for EAS)
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, createEventRecord )
{
    SEA_UNUSED_PARAM( ); 

    m_event_record_counter++;
    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Store an event index to currently parsed event record
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, storeEvent )
{
    char* name = reinterpret_cast< char* >( ACT_EVENT_PARAM( ) );
    map< string, MetaEvent >& events = m_state_machine->registry( )->m_event_map;

    map< string, MetaEvent >::iterator it = events.find( name );
    if ( it == events.end( ) )
    {
        map< string, MetaEvent >& system_events = StateMachine::system( )->registry( )->m_event_map;

        it = system_events.find( name );
        if ( it == system_events.end( ) )
        {
            SEA_TRACE( "** Parser Error: Event %s not found in %s state machine", 
                name, m_state_machine->registry( )->m_name.c_str( ) );
            push_event( Ev_Int_ParseError( ), eParserUnknownEvent );
            return;
        }
    }
    m_rt_state_machine.insert( m_rt_state_machine.end( ), it->second.index( ) );
    m_offset++;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Store an action index to currently parsed event record
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, storeAction )
{
    char* name = reinterpret_cast< char* >( ACT_EVENT_PARAM( ) );

    if ( strcmp( name, "null" ) == 0 )
    {
        m_rt_state_machine.insert( m_rt_state_machine.end( ), NULL_ACTION );
        m_offset++;

        ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
        return;
    }

    map< string, MetaAction >& actions = m_state_machine->registry( )->m_action_map;
    map< string, MetaAction >::iterator it = actions.find( name );

    if ( it == actions.end( ) )
    {
        map< string, MetaAction >& system_actions = StateMachine::system( )->registry( )->m_action_map;
        
        it = system_actions.find( name );
        if ( it == system_actions.end( ) )
        {
            SEA_TRACE( "** Parser Error: Action %s not found in % state machine", 
                name, m_state_machine->registry( )->m_name.c_str( ) );
            push_event( Ev_Int_ParseError( ), eParserUnknownAction );
            return;
        }
    }

    m_rt_state_machine.insert( m_rt_state_machine.end( ), it->second.index( ) );
    m_offset++;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// Store a next state index to currently parsed event record
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, storeNextState )
{
    String nextState = reinterpret_cast< char* >( ACT_EVENT_PARAM( ) );

    if ( nextState == "nojump" )
    {
        m_rt_state_machine.insert( m_rt_state_machine.end( ), NEXT_STATE_NOJUMP );
        m_offset++;

        ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
        return;
    }

    if ( nextState == "next" )
    {
        m_rt_state_machine.insert( m_rt_state_machine.end( ), NEXT_STATE_COUNT );
        m_offset++;

        int hbi = m_rt_state_machine[ m_current_state_offset + eHeartBeatInterval ];
        int hbc = m_rt_state_machine[ m_current_state_offset + eHeartBeatsNumber ];

        // Make sure the state is counting
        if( !( hbi == 0 && hbc > 0 ) )
        {
            push_event( Ev_Int_ParseError( ), eParserWrongCoutingStateParams );
            return;
        }

        ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
        return;
    }


    // For the second pass remember next states locations in the buffer 
    // to be resolved into actual state offsets in the same buffer
    // NextStateLocation -> name -> State offset
    m_nsl2state_name[ m_offset ] = nextState;

    // Reserve next state location
    m_rt_state_machine.insert( m_rt_state_machine.end( ), NEXT_STATE_UNDEFINED );
    m_offset++;

    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// State definition is over
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, endOfState )
{
    SEA_UNUSED_PARAM( ); 

    // Store NOR
    m_rt_state_machine[ m_current_state_offset + eNumberOfRecords ] = m_event_record_counter;
    ASSERT_DEBUG( m_rt_state_machine.size( ) == m_offset );
}

// -------------------------------------------------------------------------------------------------
// On state machine completion
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, complete )
{
    SEA_UNUSED_PARAM( ); 

    if ( m_type == eFile )
    {
        if ( m_file )
        {
            fclose( m_file );
        }
        delete m_literal_buffer;
    }
    m_rt_state_machine[ eNumberOfStates ] = m_state_index;

    // Moment of truth
#if 0
    for( size_t i = 0; i < m_rt_state_machine.size( ); ++i )
    {
        if( m_raw_array[ i ] != m_rt_state_machine[ i ] )
        {
            ASSERT_DEBUG( 0 );
        }
    }
#endif
}

// -------------------------------------------------------------------------------------------------
// On any parsing error
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, error )
{
    const char* message;
    
    m_error_code = static_cast< Error >( ( int )ACT_EVENT_PARAM( ) );

    switch( m_error_code )
    {
    case eParserNoHostingSm:
        message = "State Machine instance is not provided";
        break;
    case eParserWrongMetaData:
        message = "State Machine instance is not instantiated properly";
        break;
    case eParserCannotOpenFile:
        message = "Cannot open file";
        break;
    case eParserFileReadError:
        message = "File read error";
        break;
    case eParserEmtpySourceCode:
        message = "Source code is empy";
        break;
    case eParserUnknownSourceType:
        message = "Unknown exectution mode";
        break;
    case eParserInvalidInitialStateIndex:
        message = "Wrong initial state index";
        break;
    case eParserActionListEmpty:
        message = "Action list is empty";
        break;
    case eParserEventListEmpty:
        message = "Event list is empty";
        break;
    case eParserUnknownSystemEvent:
        message = "Unknown system event";
        break;
    case eParserUnknownSystemAction:
        message = "Unknown system action";
        break;
    case eParserUnknownEvent:
        message = "Unknown event";
        break;
    case eParserUnknownAction:
        message = "Unknown action";
        break;
    case eParserUnknownState:
        message = "Unknown state";
        break;
    case eParserUnknownStateMachine:
        message = "State machine name is not found";
        break;
    case eParserUnknownExecMode:
        message = "Unknown exec mode";
        break;
    case eParserWrongCoutingStateParams:
        message = "'next' must be used in pure COUNTING state, meaning eHeartBeatInterval == 0 && HeartBeatsNumber > 0";
        break;
    default:
        message = "Syntax error: Make sure all items are space separated";
        break;
    }
    SEA_TRACE( "** Parser Error: %s State machine:%s", message, m_state_machine->registry( )->m_name.c_str( ) );
}

// -------------------------------------------------------------------------------------------------
// Resolve states offsets
// -------------------------------------------------------------------------------------------------
ACT_IMPL( Parser, resolveNextStates )
{
    SEA_UNUSED_PARAM( ); 

    for( map< i::Offset, String >::iterator it = m_nsl2state_name.begin( );
         it != m_nsl2state_name.end( ); it++ )
    {
        ASSERT_DEBUG( m_rt_state_machine[ it->first ] == NEXT_STATE_UNDEFINED );

        map< string, i::Offset >::iterator found = m_state_name2offset.find( it->second );

        if ( found != m_state_name2offset.end( ) )
        {
            m_rt_state_machine[ it->first ] = found->second;
        }
        else
        {
            SEA_TRACE( "** Parser Error: Unknown state: %s in %s state machine", 
                const_cast< char* >( it->second.c_str( ) ), m_state_machine->registry( )->m_name.c_str( ) );
            push_event( Ev_Int_ParseError( ), eParserUnknownState );
            return;
        }
    }

#if defined( STORE_BIN_CODE )

    MetaSystemRegistryEntry* registry = m_state_machine->registry( );

    vector< int > copy( m_rt_state_machine );

    for( map< i::Offset, String >::iterator it = m_nsl2state_name.begin( );
         it != m_nsl2state_name.end( ); it++ )
    {
        map< string, i::Offset >::iterator found = m_state_name2offset.find( it->second );

        if ( found != m_state_name2offset.end( ) )
        {
            copy[ it->first ] = registry->m_offset2index[ copy[ it->first ] ];
        }
        else
        {
            ASSERT_DEBUG( false );
            return;
        }
    }

    copy[ eInitialStateIndex ] = registry->m_offset2index[ copy[ eInitialStateIndex ] ];
    copy[ eNumberOfStates ] = m_state_index;
    FILE* fp = NULL;
    String file_name = registry->m_name;
    file_name += ".sea";
    fopen_s( &fp, file_name.c_str( ), "wb" );
    if ( fp )
    {
        fprintf( fp, "int %s[ ] = {\n", registry->m_name.c_str( ) );
        for( i::uint32 i = 0; i < copy.size( ); i++ )
        {
            fprintf( fp, "    0x%08x,\n", copy[ i ] );
        }
        fputs( "};\n", fp );
        fclose( fp );
    }
#endif

    push_event( Ev_Int_ResolveOk( ) );
}

// -------------------------------------------------------------------------------------------------
